﻿#region Header
//
//   Project:           PicFx - Windows Phone 7 image effects app.
//
//   Changed by:        $Author$
//   Changed on:        $Date$
//   Changed in:        $Revision$
//   Project:           $URL$
//   Id:                $Id$
//
//
//   Copyright (c) 2010 Rene Schulte
//
//   This program is open source software. Please read the License.txt.
//
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

using Coding4Fun.Phone.Site.Controls;

using Microsoft.Devices;
using Microsoft.Phone.Tasks;

using PicFx.Effects;
using PicFx.Effects.CompositeEffects;
using PicFx.ViewModels;

namespace PicFx
{
	/// <summary>
	/// Main application page
	/// </summary>
	public partial class MainPage
	{
		WriteableBitmap _original;
		WriteableBitmap _resized;
		bool _wasResized;
		Size _oldViewportSize;
		readonly CameraCaptureTask _cameraCaptureTask;
		readonly PhotoChooserTask _photoChooserTask;
		readonly Watermarker _watermarker;
		DateTime _lastTouchUpdate;

		public MainPage()
		{
			InitializeComponent();

			// Init vars
			_watermarker = new Watermarker("data/watermark.png");
			_oldViewportSize = new Size(Viewport.ActualWidth, Viewport.ActualHeight);
			_wasResized = false;

			// Attach touch event handler
			Touch.FrameReported += Touch_FrameReported;

			// Init tasks
			_cameraCaptureTask = new CameraCaptureTask();
			_cameraCaptureTask.Completed += PhotoProviderTaskCompleted;
			_photoChooserTask = new PhotoChooserTask();
			_photoChooserTask.Completed += PhotoProviderTaskCompleted;

			// Disable the camera button if the app runs in the emulator
			// Todo: The BtnCamera reference returns null in WP7 v1
			// BtnCamera.IsEnabled = Microsoft.Devices.Environment.DeviceType != DeviceType.Emulator;
			// That's why we have to use this more hacky trick:
			var buttons = ApplicationBar.Buttons.Cast<Microsoft.Phone.Shell.ApplicationBarIconButton>();
			var btn = buttons.FirstOrDefault(b => b.IconUri.ToString().ToLower().Contains("camera"));
			if (btn != null)
			{
				btn.IsEnabled = Microsoft.Devices.Environment.DeviceType != DeviceType.Emulator;
			}
		}

		private void Initialize()
		{
			// Load and show the initial image
			if (_original == null)
			{
				_original = new WriteableBitmap(0, 0).FromResource("data/monkeys.jpg");
				ShowImage(_original);
			}
		}

		private void ApplySelectedEffectAndShowImageAsync(WriteableBitmap bitmap)
		{
			// Find selected effect
			IEffect effect = null;
			if (ListBoxEffects != null)
			{
				var item = ListBoxEffects.SelectedItem as EffectItem;
				if (item != null)
				{
					effect = item.Effect;
				}
			}

			// Apply selected effect
			if (effect == null)
			{
				// Present original
				ShowImage(bitmap);
			}
			else
			{
				var dispatcher = Dispatcher;
				ThreadPool.QueueUserWorkItem(state =>
											 {
												 // Apply Effect on int[] since WriteableBitmap can't be used in background thread
												 var width = bitmap.PixelWidth;
												 var height = bitmap.PixelHeight;
												 var resultPixels = effect.Process(bitmap.Pixels, width, height);

												 // Present result
												 // WriteableBitmap ctor has to be invoked on the UI thread
												 dispatcher.BeginInvoke(() => ShowImage(resultPixels.ToWriteableBitmap(width, height)));
											 });
			}
		}

		private void ApplySelectedEffectAndSaveAsync()
		{
			if (ListBoxEffects.SelectedItem == null)
			{
				return;
			}

			// Set Save.. state and get UI parameters
			Viewport.Opacity = 0.2;
			ProgessBar.Visibility = Visibility.Visible;
			var effect = ((EffectItem)ListBoxEffects.SelectedItem).Effect;
			var dispatcher = Dispatcher;

			ThreadPool.QueueUserWorkItem(state =>
											{
												try
												{
													// Apply Effect on int[] since WriteableBitmap can't be used in background thread
													var width = _original.PixelWidth;
													var height = _original.PixelHeight;
													var resultPixels = effect.Process(_original.Pixels, width, height);

													// Convert int[] to WriteabelBitmap
													// WriteableBitmap ctor has to be invoked on the UI thread
													dispatcher.BeginInvoke(() =>
																			{
																				// Turbo copy the pixels to the WriteableBitmap
																				var result = new WriteableBitmap(width, height);
																				Buffer.BlockCopy(resultPixels, 0, result.Pixels, 0,
																								 resultPixels.Length * 4);
																				result = _watermarker.Apply(result);

																				// Save WriteableBitmap
																				var name =
																					String.Format("PicFx_{0:yyyy-MM-dd_hh-mm-ss-tt}.jpg",
																								  DateTime.Now);

																				try
																				{
																					result.SaveToMediaLibrary(name);
																				}
																				catch (InvalidOperationException)
																				{
																					MessageBox.Show("Please remove your phone from Zune");
																				}

																			});
												}
												finally
												{
													// Set controls to initial state
													dispatcher.BeginInvoke(() =>
																			{
																				ProgessBar.Visibility = Visibility.Collapsed;
																				Viewport.Opacity = 1;
																			});
												}
											});
		}

		private void ResizeAndShowImage(WriteableBitmap bitmap)
		{
			_resized = bitmap;

			// Fast and simple resize by using UIElement rendering
			if (Viewport != null)
			{
				Viewport.Source = bitmap;
				_resized = new WriteableBitmap(Viewport, null);
			}

			// Apply selected effect and show image
			ApplySelectedEffectAndShowImageAsync(_resized);

		}

		private void ShowImage(ImageSource result)
		{
			// Show image and scroll to start page if needed
			if (Viewport != null)
			{
				Viewport.Source = result;
				if (PivotCtrl != null && PivotCtrl.SelectedIndex != 0)
				{
					PivotCtrl.SelectedIndex = 0;
				}
			}
		}

		// TODO: Extract to TiltShift parameter Viewmodel
		private void SetTiltShiftFocus(IList<TouchPoint> points)
		{
			IEffect effect = null;
			if (ListBoxEffects != null)
			{
				var item = ListBoxEffects.SelectedItem as EffectItem;
				if (item != null)
				{
					effect = item.Effect;
				}
			}

			var tiltFx = effect as TiltShiftEffect;
			if (tiltFx == null)
			{
				return;
			}
			var isManipulating = points.Any(p => p.Action == TouchAction.Down || p.Action == TouchAction.Move);
			if (isManipulating)
			{
				if (points.Count > 1 && (DateTime.Now - _lastTouchUpdate).Milliseconds > 60)
				{
					var y1 = (int)points[0].Position.Y;
					var y2 = (int)points[1].Position.Y;

					// FallOff is expected as relative coordinate
					var ih = 1f / _resized.PixelHeight;

					// Top most point is upper FallOff
					if (y1 < y2)
					{
						tiltFx.UpperFallOff = y1 * ih;
						tiltFx.LowerFallOff = y2 * ih;
					}
					else
					{
						tiltFx.UpperFallOff = y2 * ih;
						tiltFx.LowerFallOff = y1 * ih;
					}

					// Apply selected effect
					var processed = tiltFx.ProcessOnlyFocusFadeOff(_resized);

					// Add FallOff marker lines
					const int markerHeight = 4;
					processed.FillRectangle(0, y1 - markerHeight, _resized.PixelWidth, y1 + markerHeight, Colors.LightGray);
					processed.FillRectangle(0, y2 - markerHeight, _resized.PixelWidth, y2 + markerHeight, Colors.LightGray);

					Viewport.Source = processed;
					_lastTouchUpdate = DateTime.Now;
				}
			}
			else
			{
				// Apply selected effect
				Viewport.Source = tiltFx.ProcessOnlyFocusFadeOff(_resized);
			}
		}

		private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
		{
			Initialize();
		}

		private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			if (_resized != null)
			{
				// Apply selected effect and show image
				ApplySelectedEffectAndShowImageAsync(_resized);
			}
		}

		private void ApplicationBarIconFolderButton_Click(object sender, EventArgs e)
		{
			_photoChooserTask.Show();
		}

		private void ApplicationBarIconCameraButton_Click(object sender, EventArgs e)
		{
			_cameraCaptureTask.Show();
		}

		private void PhotoProviderTaskCompleted(object sender, PhotoResult e)
		{
			// Load the photo from the task result
			if (e != null && e.TaskResult == TaskResult.OK)
			{
				var bmpi = new BitmapImage();
				bmpi.SetSource(e.ChosenPhoto);
				_original = new WriteableBitmap(bmpi);

				// Remove Image control to force a full Resize operation
				// Otherwise the LayoutUpdated event gets never called for the new size
				ViewportContainer.Children.Remove(Viewport);
				Viewport.LayoutUpdated -= Viewport_LayoutUpdated;
				Viewport = new Image { Source = _original };
				Viewport.LayoutUpdated += Viewport_LayoutUpdated;
				ViewportContainer.Children.Add(Viewport);

				// Reset LayoutUpdated flags
				_oldViewportSize = new Size(0, 0);
				_wasResized = false;

				ShowImage(_original);
			}
		}

		private void ApplicationBarIconSaveButton_Click(object sender, EventArgs e)
		{
			ApplySelectedEffectAndSaveAsync();
		}

		private void ApplicationBarMenuItemAbout_Click(object sender, EventArgs e)
		{
			var about = new Coding4FunAboutPrompt();
			
			about.Show("Rene Schulte", "rschu", "picfx@rene-schulte.info", "http://blog.rene-schulte.info");
		}

		private void Viewport_LayoutUpdated(object sender, EventArgs e)
		{
			// Resize here only for the first time to get the real actual width and height
			if (Viewport != null && !_wasResized && (Viewport.ActualWidth != _oldViewportSize.Width || Viewport.ActualHeight != _oldViewportSize.Height))
			{
				ResizeAndShowImage(_original);
				_oldViewportSize = new Size(Viewport.ActualWidth, Viewport.ActualHeight);
				_wasResized = true;
			}
		}

		private void Touch_FrameReported(object sender, TouchFrameEventArgs e)
		{
			SetTiltShiftFocus(e.GetTouchPoints(Viewport));
		}
	}
}